package it.antonio.ilpizze.func.core;

import it.antonio.ilpizze.func.ComputeIterator;
import it.antonio.ilpizze.func.Mapper;
import it.antonio.ilpizze.func.Predicate;
import it.antonio.ilpizze.func.Reducer;
import it.antonio.ilpizze.func.Visit;
import it.antonio.ilpizze.func.Worker;

import java.util.Iterator;

public abstract class Iterators extends Predicates implements Core{
	@Override
	public final void mustImplementIterators() {
	}
	
	
	public static <T> boolean all(Iterator<T> it, Predicate<? super T> pred) {
		for(T element: iterable(it)) {
			if(!pred.match(element)) return false;
		}
		return  true;
	}
	
	public static <T>boolean any(Iterator<T> it, Predicate<? super T> pred) {
		for(T element: iterable(it)) {
			if(pred.match(element)) return true;
		}
		return false;
	}
	
	public static <T> Iterator<T> concat(final Iterator<T> first, final Iterator<T> second){
		return new ComputeIterator<T>() {

			@Override
			protected T computeNext() {
				if(first.hasNext()) return first.next();
				if(second.hasNext()) return second.next();
				return finished();
			}
		};
	}
	
	public static <T> Iterator<T> filter(final Iterator<T> it,  final Predicate<? super T> pred){
		return new ComputeIterator<T>() {
			@Override
			protected T computeNext() {
				if(it.hasNext()){
					T next = it.next();
					if(pred.match(next)){
						return next;
					} else {
						return computeNext();
					}
				}
				return finished();
			}
		};
		
	}
	
	
	// UTILS
	
	public static <T> T  find(Iterator<T> it, Predicate<? super T> pred) {
		for(T element: iterable(it)) {
			if(pred.match(element)) return element;
		}
		return null;
	}

	public static <T> T first(Iterator<T> it) {
		if(it.hasNext()){
			return it.next();
		}
		return null;
	}
	
	public static <T> void  forEach(Iterator<T> it, Worker<? super T> worker) {
		for(T element: iterable(it)) worker.on(element);
	}
	private static <T> Iterable<T> iterable(final Iterator<T> it){
		return new Iterable<T>() {

			@Override
			public Iterator<T> iterator() {
				return it;
			}
		};
	}
	
	public static <T> T last(Iterator<T> it) {
		T instance = null;
		while(it.hasNext()){
			instance = it.next();
		}
		return instance;
	}
	
	public static <T,Y> Iterator<Y> map(final Iterator<T> it, final Mapper<? super T, ? extends Y> mapper){
		return new ComputeIterator<Y>() {
			
			@Override
			protected Y computeNext() {
				if(it.hasNext()){
					return mapper.map(it.next());
				}
				return finished();
			}
			
			
		};
	}
	public static <T, R> R reduce(Iterator<T> it, R start, Reducer<R, T> reducer) {
		R accumulator = start;
		for(T element: iterable(it)) {
			accumulator = reducer.reduce(accumulator, element);
		}
		return accumulator;
	}

	public static <T, R> R reduce(Iterator<T> it, Reducer<R, T> reducer) {
		return reduce(it, null, reducer);
	}

	public static <T> Iterator<T> truncate(final Iterator<T> it,final int size) {
		return new ComputeIterator<T>() {
			private int count = 0;
			@Override
			protected T computeNext() {
				if(it.hasNext() && count < size){
					T next = it.next();
					count++;
					return next;
				}
				return finished();
			}
		};
	}
	
	public static <T> void visit(Iterator<T> it, Visit<? super T> visit) {
		for(T element: iterable(it)) {
			if(visit.on(element) == Visit.STOP) break; 
		}
	}
	
}
